#ifndef _CIRCULAR_BUFFER_H
#define _CIRCULAR_BUFFER_H

#include <cassert>

#include "BufferErrorPolicies.h"

template<
	class T,
	template<class> class WriteErrorPolicy = WriteErrorPolicyException,
	template<class> class ReadErrorPolicy = ReadErrorPolicyException
>
class CircularBuffer
{
public:
	typedef T& reference;
	typedef const T& const_reference;

	CircularBuffer(size_t size) : 
		m_pData(new T[size]),
		m_iSize(size),
		m_iContentSize(0),
		m_iHead(0),
		m_iTail(0)
	{
	}

	~CircularBuffer()
	{
		delete [] m_pData;
		m_pData = nullptr;
	}

	// Random Access
	reference operator[](size_t idx)
	{
		if (empty()) 
			return ReadErrorPolicy<T>::HANDLE_ERROR();
		return m_pData[idx];
	}
	
	const_reference operator[](size_t idx) const
	{
		assert(idx < m_iContentSize);
		if (empty()) 
			return ReadErrorPolicy<T>::HANDLE_ERROR();
		return static_cast<const T&>(m_pData[idx]);
	}

	size_t size() const
	{
		return m_iSize;
	}

    bool empty() const
	{
		return (m_iContentSize == 0);
	}

	reference front()
	{
		if (empty()) 
			return ReadErrorPolicy<T>::HANDLE_ERROR();
		return m_pData[m_iHead];
	}
	
	const_reference front() const
	{
		if (empty()) 
			return ReadErrorPolicy<T>::HANDLE_ERROR();
		return static_cast<const_reference>(m_pData[m_iHead]);
	}

	reference back()
	{
		if (empty()) 
			return ReadErrorPolicy<T>::HANDLE_ERROR();
		return m_pData[m_iTail];
	}
	
	const_reference back() const
	{
		if (empty()) 
			return ReadErrorPolicy<T>::HANDLE_ERROR();
		return static_cast<const_reference>(m_pData[m_iTail]);
	}

	void pop_front()
	{
		increment_head();
	}

	void push_back(const_reference value)
	{
		if (m_iContentSize == 0)
		{
			m_pData[m_iHead] = value;
			m_iTail = m_iHead;
			++m_iContentSize;
		}
		else if (m_iContentSize != m_iSize)
		{
			increment_tail();
			m_pData[m_iTail] = value;
		}
		else 
		{
			// policy must return true if oldest should be overwritten
			if (WriteErrorPolicy<T>::HANDLE_ERROR())
			{
				increment_head();
				increment_tail();
				m_pData[m_iTail] = value;
			}
		} 
	}


private:
	T*		m_pData;
	size_t	m_iSize;
	size_t	m_iContentSize;
	size_t	m_iHead;
	size_t	m_iTail;

	void increment_tail()
	{
		++m_iTail;
		++m_iContentSize;
		if (m_iTail == m_iSize) m_iTail = 0;
	}

	void increment_head()
	{
		++m_iHead;
		--m_iContentSize;
		if (m_iHead == m_iSize) m_iHead = 0;
	}
};

#endif /* _CIRCULAR_BUFFER_H */


